package compilers.AST.expression;

import compilers.AST.Node;
import compilers.AST.types.MyType;
import compilers.AST.types.TypeSpecifier;
import compilers.compiler.PGenerator;
import compilers.exceptions.SemanticalException;
import compilers.symbolTable.SymbolTableScope;

public class Relation extends Node {
	
	public final static int EQUALS = 1;
	public final static int NEQUALS = 2;
	public final static int LESS = 3;
	public final static int LESSEQ = 4;
	public final static int GREATEREQ = 5;
	public final static int GREATER = 6;

	private int fComparator;
	private Sum fFirstSum, fSecondSum;
	
	public Relation() {
		fComparator = 0; 
		fSecondSum = null;
	}
	
	public Relation(int lineNr) {
		super(lineNr);
		fComparator = 0; 
		fSecondSum = null;
	}
	
	public void print(int identLevel) {
		println(identLevel, "Relation");
		fFirstSum.print(identLevel+1);
		if (fComparator > 0) {
			String opName = "";
			switch (fComparator) {
				case EQUALS:
					opName = "==";
					break;
				case NEQUALS:
					opName = "!=";
					break;
				case LESS:
					opName = "<";
					break;
				case LESSEQ:
					opName = "<=";
					break;
				case GREATEREQ:
					opName = ">=";
					break;
				case GREATER:
					opName = ">";
					break;
			}
			println(identLevel+1, "Comparator = " + opName);
			fSecondSum.print(identLevel+1);
		}
	}
	
	public void checkSemantics(SymbolTableScope currentScope) throws SemanticalException {
		fFirstSum.checkSemantics(currentScope);
		if (fSecondSum != null) {
			fSecondSum.checkSemantics(currentScope);
			
			// All types for EQUALS and NEQUALS (except void type)
			if ((fComparator == EQUALS) || (fComparator == NEQUALS)) {
				if (fFirstSum.getResultType(currentScope) != fSecondSum.getResultType(currentScope)) {
					throw new SemanticalException("The types in the relation expression should be of the same type.", fLineNr);
				}
			
			// Numerical types for LESS, LESSEQ, GREATEREQ and GREATER
			} else {
				if ( (fFirstSum.getResultType(currentScope) != TypeSpecifier.INTEGER) && (fFirstSum.getResultType(currentScope) != TypeSpecifier.FLOAT)) {
					throw new SemanticalException("The type in the relation expression is jnot of the requested numerical types.", fLineNr);
				} else if ( (fSecondSum.getResultType(currentScope) != TypeSpecifier.INTEGER) && (fSecondSum.getResultType(currentScope) != TypeSpecifier.FLOAT)) {
					throw new SemanticalException("The type in the relation expression is not of the requested numerical types.", fLineNr);
				}
			}
		}
	}
	
	public void setComparator(int comparator) {
		fComparator = comparator;
	}
	
	public void setFirstSum(Sum sum) {
		fFirstSum = sum;
	}
	
	public void setSecondSum(Sum sum) {
		fSecondSum = sum;
	}
	
	public int getResultType(SymbolTableScope currentScope) {
		if (fSecondSum != null) {
			return TypeSpecifier.BOOLEAN;
		} else {
			return fFirstSum.getResultType(currentScope);
		}
	}
	
	public int getOriginalType(SymbolTableScope currentScope) {
		if (fSecondSum != null) {
			return TypeSpecifier.BOOLEAN;
		} else {
			return fFirstSum.getOriginalType(currentScope);
		}
	}
	
	public boolean isConstant(SymbolTableScope currentScope) {
		if(fSecondSum == null) {
			return fFirstSum.isConstant(currentScope);
		} else {
			return (fFirstSum.isConstant(currentScope) && fSecondSum.isConstant(currentScope));
		}
	}

	public boolean isArray(SymbolTableScope currentScope) {
		if (fSecondSum == null) {
			return fFirstSum.isArray(currentScope);
		} else {
			return false;
		}
	}
	
	public String getArrayIdent(SymbolTableScope currentScope) {
		return fFirstSum.getArrayIdent(currentScope);
	}
	
	public MyType calculateValue(SymbolTableScope currentScope) {
		if(fSecondSum == null) {
			return fFirstSum.calculateValue(currentScope);
		} else {
			switch (fComparator) {
				case EQUALS:
					return (fFirstSum.calculateValue(currentScope).equalsRel(fSecondSum.calculateValue(currentScope)));
				case NEQUALS:
					return (fFirstSum.calculateValue(currentScope).nequals(fSecondSum.calculateValue(currentScope)));
				case LESS:
					return (fFirstSum.calculateValue(currentScope).less(fSecondSum.calculateValue(currentScope)));
				case LESSEQ:
					return (fFirstSum.calculateValue(currentScope).lessEq(fSecondSum.calculateValue(currentScope)));
				case GREATEREQ:
					return (fFirstSum.calculateValue(currentScope).greaterEq(fSecondSum.calculateValue(currentScope)));
				case GREATER:
					return (fFirstSum.calculateValue(currentScope).greater(fSecondSum.calculateValue(currentScope)));
			}
		}
		return null;
	}
	
	public void generatePcode(PGenerator pGen, SymbolTableScope currentScope) {
		if (fFirstSum.isConstant(currentScope)) {
			TypeSpecifier firstResultType = new TypeSpecifier(fFirstSum.getResultType(currentScope));
			pGen.load(firstResultType.getPType(currentScope), fFirstSum.calculateValue(currentScope).valueToString());
		} else {
			fFirstSum.generatePcode(pGen, currentScope);
		}
		if (fSecondSum != null) {
			TypeSpecifier secondResultType = new TypeSpecifier(fSecondSum.getResultType(currentScope));
			if (fFirstSum.getResultType(currentScope) != fSecondSum.getResultType(currentScope)) {
				if (fFirstSum.getResultType(currentScope) == TypeSpecifier.INTEGER) {
					pGen.conv(TypeSpecifier.getPType(fFirstSum.getOriginalType(currentScope)), TypeSpecifier.getPType(TypeSpecifier.FLOAT));
				}
			}
			if (fSecondSum.isConstant(currentScope)) {
				pGen.load(secondResultType.getPType(currentScope), fSecondSum.calculateValue(currentScope).valueToString());
			} else {
				fSecondSum.generatePcode(pGen, currentScope);
			}
			int resultType = secondResultType.getType(currentScope);
			if (fFirstSum.getResultType(currentScope) != fSecondSum.getResultType(currentScope)) {
				if (fSecondSum.getResultType(currentScope) == TypeSpecifier.INTEGER) {
					pGen.conv(TypeSpecifier.getPType(fSecondSum.getOriginalType(currentScope)), TypeSpecifier.getPType(TypeSpecifier.FLOAT));
				}
				resultType = TypeSpecifier.FLOAT;
			}
			char type = TypeSpecifier.getPType(resultType);
			
			
			
			switch (fComparator) {
				case EQUALS:
					pGen.equalsRel(type);
					break;
				case NEQUALS:
					pGen.nEquals(type);
					break;
				case LESS:
					pGen.less(type);
					break;
				case LESSEQ:
					pGen.lessEq(type);
					break;
				case GREATEREQ:
					pGen.greaterEq(type);
					break;
				case GREATER:
					pGen.greater(type);
					break;
			}
		}
	}
	
}
